package com.uniview.annotation;

import com.google.common.collect.Maps;
import com.uniview.properties.OpenScanProperties;
import com.uniview.constants.QueueConstants;
import com.uniview.utils.EncryptUtils;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.MethodParameter;
import org.springframework.core.env.Environment;
import org.springframework.http.MediaType;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import springfox.documentation.annotations.ApiIgnore;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @Author lux
 * @date: 2019/12/17
 * @desc: 自定义注解扫描
 */
@Slf4j
public class RequestMappingScan implements ApplicationListener<ApplicationReadyEvent> {
    private AmqpTemplate amqpTemplate;
    private static final AntPathMatcher pathMatch = new AntPathMatcher();
    private OpenScanProperties scanProperties;

    public RequestMappingScan(AmqpTemplate amqpTemplate, OpenScanProperties scanProperties) {
        this.amqpTemplate = amqpTemplate;
        this.scanProperties = scanProperties;
    }

    private ExecutorService executorService = Executors.newFixedThreadPool(2);

    /**
     * 初始化方法
     *
     * @param event
     */
    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        ConfigurableApplicationContext applicationContext = event.getApplicationContext();
        if (null == event.getApplicationContext().getParent().getParent()) {
            return;
        }
        amqpTemplate = applicationContext.getBean(RabbitTemplate.class);
        if (amqpTemplate == null || scanProperties == null || !scanProperties.isRegisterRequestMapping()) {
            return;
        }
        Environment env = applicationContext.getEnvironment();
        // 服务名称
        String serviceId = env.getProperty("spring.application.name", "application");
        // 所有接口映射
        RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
        // 获取url与类和方法的对应信息
        Map<RequestMappingInfo, HandlerMethod> handlerMethods = mapping.getHandlerMethods();
        //获取不需要校验的urlList
        String[] permitAll = scanProperties.getUrl().split("\\,");
        //获取handlerMethods的具体信息
        List<Map<String, String>> list = new ArrayList<Map<String, String>>();
        for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : handlerMethods.entrySet()) {
            RequestMappingInfo requestMappingInfo = entry.getKey();
            HandlerMethod handlerMethod = entry.getValue();
            //只扫描RestController
            if (handlerMethod.getMethod().getDeclaringClass().getAnnotation(RestController.class) == null) {
                // 只扫描RestController
                continue;
            }
            //忽视的接口不扫描
            if (handlerMethod.getMethodAnnotation(ApiIgnore.class) != null) {
                // 忽略的接口不扫描
                continue;
            }
            Set<MediaType> mediaTypeSet = requestMappingInfo.getProducesCondition().getProducibleMediaTypes();
            for (MethodParameter params : handlerMethod.getMethodParameters()) {
                if (params.hasParameterAnnotation(RequestBody.class)) {
                    mediaTypeSet.add(MediaType.APPLICATION_JSON_UTF8);
                    break;
                }
            }
            String mediaTypes = getMediaTypes(mediaTypeSet);
            // 请求类型
            RequestMethodsRequestCondition methodsCondition = requestMappingInfo.getMethodsCondition();
            String methods = getMethods(methodsCondition.getMethods());
            // 请求路径
            PatternsRequestCondition p = requestMappingInfo.getPatternsCondition();
            String urls = getUrls(p.getPatterns());
            Map<String, String> api = Maps.newHashMap();
            // 类名
            String className = handlerMethod.getMethod().getDeclaringClass().getName();
            // 方法名
            String methodName = handlerMethod.getMethod().getName();
            String fullName = className + "." + methodName;
            // md5码
            String md5 = EncryptUtils.md5Hex(serviceId + urls);
            String name = "";
            String desc = "";
            // 是否需要安全认证 默认:1-是 0-否
            String isAuth = "1";
            // 匹配项目中.permitAll()配置
            for (String url : p.getPatterns()) {
                for (String permitUrl : permitAll) {
                    if (pathMatch.match(permitUrl, url)) {
                        // 忽略验证
                        isAuth = "0";
                    }
                }
            }
            ApiOperation apiOperation = handlerMethod.getMethodAnnotation(ApiOperation.class);
            if (apiOperation != null) {
                name = apiOperation.value();
                desc = apiOperation.notes();
            }
            name = StringUtils.isBlank(name) ? methodName : name;
            api.put("apiName", name);
            api.put("apiCode", md5);
            api.put("apiDesc", desc);
            api.put("path", urls);
            api.put("className", className);
            api.put("methodName", methodName);
            api.put("md5", md5);
            api.put("requestMethod", methods);
            api.put("serviceId", serviceId);
            api.put("contentType", mediaTypes);
            api.put("isAuth", isAuth);
            api.put("fullName", fullName);
            list.add(api);
        }
        Map resource = Maps.newHashMap();
        resource.put("application", serviceId);
        resource.put("mapping", list);
        log.info("ApplicationReadyEvent:[{}]", serviceId);
        executorService.submit(() -> {
            try {
                amqpTemplate.convertAndSend(QueueConstants.QUEUE_SCAN_API_RESOURCE, resource);
                log.info("发送成功:{}");
            } catch (Exception e) {
                log.error("发送失败:{}", e);
            }
        });
    }

    /**
     * @param mediaTypes
     * @desc 获取媒体类型集合
     */
    private String getMediaTypes(Set<MediaType> mediaTypes) {
        StringBuilder sbf = new StringBuilder();
        for (MediaType mediaType : mediaTypes) {
            sbf.append(mediaType.toString()).append(",");
        }
        if (mediaTypes.size() > 0) {
            sbf.deleteCharAt(sbf.length() - 1);
        }
        return sbf.toString();
    }

    /**
     * @param requestMethods
     * @desc 获取请求方式
     */
    private String getMethods(Set<RequestMethod> requestMethods) {
        StringBuilder sbf = new StringBuilder();
        for (RequestMethod requestMethod : requestMethods) {
            sbf.append(requestMethod.toString()).append(",");
        }
        if (requestMethods.size() > 0) {
            sbf.deleteCharAt(sbf.length() - 1);
        }
        return sbf.toString();
    }

    /**
     * @param urls
     * @desc 获取urls
     */
    private String getUrls(Set<String> urls) {
        StringBuilder sbf = new StringBuilder();
        for (String url : urls) {
            sbf.append(url).append(",");
        }
        if (urls.size() > 0) {
            sbf.deleteCharAt(sbf.length() - 1);
        }
        return sbf.toString();
    }
}
